package com.uinnova.product.eam.service.impl;

import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.binary.core.exception.BinaryException;
import com.binary.core.util.BinaryUtils;
import com.binary.jdbc.Page;
import com.uinnova.product.eam.base.exception.ServerException;
import com.uinnova.product.eam.base.util.EamUtil;
import com.uinnova.product.eam.model.asset.EamCiRltDTO;
import com.uinnova.product.eam.model.dto.DiagramSnapshotParam;
import com.uinnova.product.eam.model.dto.DiagramSnapshotVO;
import com.uinnova.product.eam.service.DiagramSnapshotSvc;
import com.uinnova.product.eam.service.FxDiagramSvc;
import com.uinnova.product.eam.service.IEamCIClassApiSvc;
import com.uinnova.product.eam.service.bm.impl.FlowModelMergePreProcessor;
import com.uinnova.product.eam.service.es.*;
import com.uinnova.product.eam.service.utils.DataModelDiagramUtil;
import com.uinnova.product.vmdb.comm.model.ci.CCcCi;
import com.uinnova.product.vmdb.comm.model.ci.CCcCiClass;
import com.uinnova.product.vmdb.comm.model.ci.CcCiAttrDef;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiClassInfo;
import com.uinnova.product.vmdb.provider.ci.bean.CcCiInfo;
import com.uinnova.project.base.diagram.comm.model.*;
import com.uinnova.project.base.diagram.enums.DiagramCopyEnum;
import com.uinnova.project.db.diagram.VcDiagramVersionDao;
import com.uinnova.project.db.eam.ESDiagramDao;
import com.uinnova.project.db.eam.ESDiagramLinkDao;
import com.uinnova.project.db.eam.ESDiagramNodeDao;
import com.uinnova.project.db.eam.ESDiagramSheetDao;
import com.uinnova.project.service.eam.ESDiagramExtendSvc;
import com.uinnova.project.service.eam.ESDiagramSvc;
import com.uinnova.project.service.eam.ESDiagramVersionSvc;
import com.uino.api.client.permission.IUserApiSvc;
import com.uino.bean.cmdb.base.*;
import com.uino.bean.cmdb.query.ESRltSearchBean;
import com.uino.bean.permission.base.SysUser;
import com.uino.bean.permission.query.CSysUser;
import com.uino.dao.cmdb.ESCIClassSvc;
import com.uino.dao.util.ESUtil;
import com.uino.util.sys.CheckAttrUtil;
import com.uino.util.sys.SysUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

import static com.uino.util.sys.CheckAttrUtil.toStdMap;

@Slf4j
@Service
public class DiagramSnapshotSvcImpl implements DiagramSnapshotSvc {

    @Autowired
    IamsESCIPrivateSvc iamsESCIPrivateSvc;
    @Autowired
    IamsCIRltPrivateSvc iamsCIRltPrivateSvc;
    @Autowired
    ESDiagramSvc esDiagramSvc;
    @Autowired
    ESDiagramExtendSvc esDiagramExtendSvc;
    @Autowired
    ESDiagramDao esDiagramDao;
    @Autowired
    ESDiagramVersionSvc esDiagramVersionSvc;
    @Autowired
    FlowModelMergePreProcessor mergePreProcessor;
    @Autowired
    ESDiagramNodeDao esDiagramNodeDao;
    @Autowired
    ESDiagramLinkDao esDiagramLinkDao;
    @Autowired
    ESDiagramSheetDao esDiagramSheetDao;
    @Autowired
    IamsESCIHistoryPrivateSvc ciHistorySvc;
    @Autowired
    IamsESCIRltInfoHistoryPrivateSvc rltHistorySvc;
    @Autowired
    FxDiagramSvc fxDiagramSvc;
    @Autowired
    ESCIClassSvc esciClassSvc;
    @Autowired
    VcDiagramVersionDao diagramVersionDao;
    @Autowired
    IUserApiSvc iUserApiSvc;
    @Autowired
    IEamCIClassApiSvc ciClassApiSvc;

    private static final String SNAPSHOT = "SNAPSHOT";

    private static final String ROLL_BACK = "rollBack";
    private static final String CREATE = "create";

    public static ExecutorService DIAGRAM_SNAPSHOT_EXECUTOR = Executors.newFixedThreadPool(10);

    @Override
    public Map<String, String> generateSnapshotByDiagramIds(List<DiagramSnapshotVO> diagramSnapshotVOS, SysUser currentUserInfo, Boolean isAuto) {
        Map<String, String> idAndNameMap = diagramSnapshotVOS.stream().collect(Collectors.toMap(vo->vo.getMainDiagramId(), vo->Optional.ofNullable(vo.getSnapshotName()).orElse(""), (k1, k2)->k1));
        Set<String> diagramIds = idAndNameMap.keySet();
        Map<String, String> snapshotMap = new HashMap<>();
        if (CollectionUtils.isEmpty(diagramIds)) {
            return snapshotMap;
        }
        // 预制视图快照ID
        Map<Long, Long> diagramSnapshotMap = generateDiagramSnapshot(diagramIds);
        for (Long key : diagramSnapshotMap.keySet()) {
            snapshotMap.put(SecureUtil.md5(String.valueOf(key)).substring(8, 24), SecureUtil.md5(String.valueOf(diagramSnapshotMap.get(key))).substring(8, 24));
        }
        log.info("###########返回值snapshotMap【{}】", JSONObject.toJSONString(snapshotMap));
        // 获取用户全量详情
        SysUser currentUser = queryCurUserInfoByCode(currentUserInfo.getLoginCode());
        // 生成视图快照信息
        esDiagramVersionSvc.createSnapshotByCurrVersion(diagramSnapshotMap, idAndNameMap, currentUser, isAuto);
        // 更新主视图以及关联历史版本数据修改时间
        updateModifyTime(diagramSnapshotMap.keySet(), currentUserInfo);
        // 处理视图快照及视图关联的CI/RLT生成
        if (isAuto) {
            generateSnapshot(diagramSnapshotMap, diagramIds, currentUserInfo);
        } else {
            CompletableFuture.runAsync(()->{
                generateSnapshot(diagramSnapshotMap, diagramIds, currentUserInfo);
            }, DIAGRAM_SNAPSHOT_EXECUTOR).exceptionally(ex -> {
                log.error("#############快照内容生成异常,异常信息");
                ex.printStackTrace();
                return null;
            });
        }
        return snapshotMap;
    }

    @Override
    public Map<String, List<CcCiInfo>> querySnapshotCIByHistoryIds(List<String> hisDiagramIds, List<String> ciCodes) {
        Map<String, List<CcCiInfo>> ciData = new HashMap<>();
        // 获取所有node信息
        BoolQueryBuilder nodeBool = QueryBuilders.boolQuery();
        nodeBool.must(QueryBuilders.termsQuery("dEnergy.keyword", hisDiagramIds));
        if (!BinaryUtils.isEmpty(ciCodes)) {
            nodeBool.must(QueryBuilders.termsQuery("ciCode.keyword", ciCodes));
        }
        List<ESDiagramNode> nodeList = esDiagramNodeDao.getListByQuery(nodeBool);
        // 根据视图将节点分组
        Map<String, List<ESDiagramNode>> diagramNodeMap = nodeList.stream().collect(Collectors.groupingBy(node -> node.getdEnergy()));
        // 批量获取一下视图对应的用户信息 醉了
        List<ESDiagram> esDiagrams = esDiagramSvc.queryDBDiagramInfoByIds(hisDiagramIds.toArray(new String[0]));
        Map<String, String> idUserCodeMap = esDiagrams.stream().collect(Collectors.toMap(ESDiagram::getDEnergy, ESDiagram::getOwnerCode, (k1, k2)->k1));
        // 使用ciCode+version查询历史库数据 TODO ER图是否需要特殊处理一下
        Map<String, ESDiagramNode> ciCodeNodeMap = new HashMap<>();
        // 兼容一下未绑定版本号的数据 用业务主键查询
        List<String> nohasVersionList = new ArrayList<>();
        for (ESDiagramNode node : nodeList) {
            String ciCode = node.getCiCode();
            Long version = node.getVersion();
            if (!BinaryUtils.isEmpty(ciCode)) {
                if (BinaryUtils.isEmpty(version)) {
                    // 正常不会有这种数据
                    nohasVersionList.add(ciCode);
                } else {
                    ciCodeNodeMap.put(ciCode, node);
                }
            }
        }
        if (!BinaryUtils.isEmpty(ciCodeNodeMap)) {
            BoolQueryBuilder ciHistoryQuery = QueryBuilders.boolQuery();
            for (String ciCode : ciCodeNodeMap.keySet()) {
                ESDiagramNode node = ciCodeNodeMap.get(ciCode);
                BoolQueryBuilder oneQuery = QueryBuilders.boolQuery();
                oneQuery.must(QueryBuilders.termQuery("version", node.getVersion()));
                oneQuery.must(QueryBuilders.termQuery("ciCode.keyword", ciCode));
                oneQuery.must(QueryBuilders.termQuery("ownerCode.keyword", idUserCodeMap.get(node.getdEnergy())));
                ciHistoryQuery.should(oneQuery);
            }
            List<ESCIHistoryInfo> snapshotList = ciHistorySvc.getListByQuery(ciHistoryQuery);
            // 查询分类信息
            Set<Long> classIds = snapshotList.stream().map(ESCIHistoryInfo::getClassId).collect(Collectors.toSet());
            CCcCiClass cdt = new CCcCiClass();
            cdt.setIds(classIds.toArray(new Long[classIds.size()]));
            List<CcCiClassInfo> classInfoList = esciClassSvc.queryCiClassInfoList(cdt, null, false);
            // 转换CI数据格式
            List<CcCiInfo> snapshotCiInfos = fxDiagramSvc.coverCIHisInfoToCIInfoNew(snapshotList, classInfoList, "SNAPSHOT");
            for (String diagramId : diagramNodeMap.keySet()) {
                List<ESDiagramNode> nodes = diagramNodeMap.get(diagramId);
                List<String> nodeCiCode = nodes.stream().map(ESDiagramNode::getCiCode).collect(Collectors.toList());
                List<CcCiInfo> filterData = snapshotCiInfos.stream().filter(e->nodeCiCode.contains(e.getCi().getCiCode())).collect(Collectors.toList());
                ciData.put(diagramId, filterData);
            }
        }
        if (!BinaryUtils.isEmpty(nohasVersionList)) {
            // 这块如果是主键冲突的数据刷新过节点 就查不到了 查不到先不管
            log.info("#############nohasVersionList:【{}】", JSONObject.toJSONString(nohasVersionList));
        }
        return ciData;
    }

    @Override
    public Map<String, List<EamCiRltDTO>> querySnapshotRltByHistoryIds(List<String> hisDiagramIds, List<String> rltCodes) {
        Map<String, List<EamCiRltDTO>> rltData = new HashMap<>();
        // 获取所有link信息
        BoolQueryBuilder linkBool = QueryBuilders.boolQuery();
        linkBool.must(QueryBuilders.termsQuery("dEnergy.keyword", hisDiagramIds));
        if (!BinaryUtils.isEmpty(rltCodes)) {
            linkBool.must(QueryBuilders.termsQuery("uniqueCode.keyword", rltCodes));
        }
        List<ESDiagramLink> linkList = esDiagramLinkDao.getListByQuery(linkBool);
        // 根据视图将节点分组
        Map<String, List<ESDiagramLink>> diagramLinkMap = linkList.stream().collect(Collectors.groupingBy(link -> link.getdEnergy()));
        List<ESDiagram> esDiagrams = esDiagramSvc.queryDBDiagramInfoByIds(hisDiagramIds.toArray(new String[0]));
        Map<String, String> idUserCodeMap = esDiagrams.stream().collect(Collectors.toMap(ESDiagram::getDEnergy, ESDiagram::getOwnerCode, (k1, k2)->k1));
        Map<String, ESDiagramLink> uniqueCodeLinkMap = new HashMap<>();
        // 兼容一下未绑定版本号的数据 用业务主键查询
        List<String> nohasVersionList = new ArrayList<>();
        for (ESDiagramLink link : linkList) {
            String uniqueCode = link.getUniqueCode();
            Long version = link.getVersion();
            if (!BinaryUtils.isEmpty(uniqueCode)) {
                if (BinaryUtils.isEmpty(version)) {
                    // 正常不会有这种数据
                    nohasVersionList.add(uniqueCode);
                } else {
                    uniqueCodeLinkMap.put(uniqueCode, link);
                }
            }
        }
        if (!BinaryUtils.isEmpty(uniqueCodeLinkMap)) {
            BoolQueryBuilder rltHistoryQuery = QueryBuilders.boolQuery();
            for (String uniqueCode : uniqueCodeLinkMap.keySet()) {
                ESDiagramLink link = uniqueCodeLinkMap.get(uniqueCode);
                BoolQueryBuilder oneQuery = QueryBuilders.boolQuery();
                oneQuery.must(QueryBuilders.termQuery("version", link.getVersion()));
                oneQuery.must(QueryBuilders.termQuery("uniqueCode.keyword", uniqueCode));
                oneQuery.must(QueryBuilders.termQuery("ownerCode.keyword", idUserCodeMap.get(link.getdEnergy())));
                rltHistoryQuery.should(oneQuery);
            }
            List<ESCIRltInfoHistory> snapshotList = rltHistorySvc.getListByQuery(rltHistoryQuery);
            // 私有库数据查询条件要加上ownerCode 暂时先根据用户分组转换
            Map<String, List<ESCIRltInfoHistory>> userCodeDataMap = snapshotList.stream().collect(Collectors.groupingBy(rlt -> rlt.getOwnerCode()));
            List<EamCiRltDTO> allSnapshotRltInfos = new ArrayList<>();
            // todo 这里可以优化一下
            for (String userCode : userCodeDataMap.keySet()) {
                List<EamCiRltDTO> snapshotRltInfos = fxDiagramSvc.coverRltHisInfoToESInfo(userCodeDataMap.get(userCode), LibType.PRIVATE, userCode);
                allSnapshotRltInfos.addAll(snapshotRltInfos);
            }
            for (String diagramId : diagramLinkMap.keySet()) {
                List<ESDiagramLink> links = diagramLinkMap.get(diagramId);
                List<String> linkUnique = links.stream().map(ESDiagramLink::getUniqueCode).collect(Collectors.toList());
                List<EamCiRltDTO> filterData = allSnapshotRltInfos.stream().filter(e->linkUnique.contains("UK_" + e.getCiRlt().getCiCode())).collect(Collectors.toList());
                rltData.put(diagramId, filterData);
            }
        }
        if (!BinaryUtils.isEmpty(nohasVersionList)) {
            // 这块如果是主键冲突的数据刷新过节点 就查不到了 查不到先不管
            log.info("#############nohasVersionList:【{}】", JSONObject.toJSONString(nohasVersionList));
        }
        return rltData;
    }

    @Override
    public String saveOrUpdateDiagramBySnapshotId(String snapshotId, String type) {
        DiagramSnapshotParam snapshotParam = new DiagramSnapshotParam();
        snapshotParam.setSnapshotId(snapshotId);
        snapshotParam.setType(type);
        // 获取数据
        querySnapshotInfo(snapshotParam);
        querySourceAndMainDiagramInfoNew(snapshotParam);
        querySourceCiAndRltInfo(snapshotParam);
        // todo 更新或新建数据
        updateLatestCiAndRlt(snapshotParam);
        saveOrUpdateDiagramInfo(snapshotParam);
        // todo 刷新新视图内的节点关系数据
        refreshDiagramNodeAndLink(snapshotParam);
        return snapshotParam.getReturnDiagramId();
    }

    @Override
    public Boolean updateSnapshotInfo(DiagramSnapshotVO snapshotVO) {
        List<ESDiagram> snapshotList = esDiagramDao.getListByQuery(QueryBuilders.termQuery("dEnergy.keyword", snapshotVO.getSnapshotId()));
        if (BinaryUtils.isEmpty(snapshotList)) {
            throw new ServerException("根据标识为获取到视图历史数据");
        }
        ESDiagram snapshotInfo = snapshotList.get(0);
        snapshotInfo.setName(snapshotVO.getSnapshotName());
        VcDiagramVersion cdt = new VcDiagramVersion();
        cdt.setName(snapshotVO.getSnapshotName());
        // 更新数据
        diagramVersionDao.updateById(cdt, snapshotInfo.getId());
        esDiagramDao.saveOrUpdate(snapshotInfo);
        return Boolean.TRUE;
    }

    @Override
    public Boolean deleteSnapshotInfo(DiagramSnapshotVO snapshotVO) {
        List<ESDiagram> snapshotList = esDiagramDao.getListByQuery(QueryBuilders.termQuery("dEnergy.keyword", snapshotVO.getSnapshotId()));
        if (BinaryUtils.isEmpty(snapshotList)) {
            throw new ServerException("根据标识为获取到视图历史数据");
        }
        ESDiagram snapshotInfo = snapshotList.get(0);
        diagramVersionDao.deleteById(snapshotInfo.getId());
        esDiagramDao.deleteById(snapshotInfo.getId());
        esDiagramLinkDao.deleteByQuery(QueryBuilders.termQuery("dEnergy.keyword", snapshotVO.getSnapshotId()), Boolean.FALSE);
        esDiagramNodeDao.deleteByQuery(QueryBuilders.termQuery("dEnergy.keyword", snapshotVO.getSnapshotId()), Boolean.FALSE);
        esDiagramSheetDao.deleteByQuery(QueryBuilders.termQuery("dEnergy.keyword", snapshotVO.getSnapshotId()), Boolean.FALSE);
        return Boolean.TRUE;
    }

    @Override
    public Boolean isPrivateHistoryInfo(String diagramId) {
        Boolean isHistory = Boolean.FALSE;
        if (!BinaryUtils.isEmpty(diagramId)) {
            // 兼容一下私有历史库的数据查询
            List<ESDiagram> listByQuery = esDiagramDao.getListByQuery(QueryBuilders.termQuery("dEnergy.keyword", diagramId));
            if (!BinaryUtils.isEmpty(listByQuery)) {
                ESDiagram esDiagram = listByQuery.get(0);
                isHistory = esDiagram.getHistoryVersionFlag() == 0 && esDiagram.getIsOpen() == 0 ? Boolean.TRUE : Boolean.FALSE;
            }
        }
        return isHistory;
    }

    private void generateSnapshot(Map<Long, Long> diagramSnapshotMap, Set<String> diagramIds, SysUser currentUserInfo) {
        Long[] snapshotIds = diagramSnapshotMap.values().toArray(new Long[0]);
        // 数据根据用户分组操作
        List<ESDiagramDTO> diagramDTOS = esDiagramSvc.queryDiagramInfoByIds(snapshotIds, SNAPSHOT, Boolean.FALSE, Boolean.FALSE);
        Map<String, List<ESDiagramDTO>> diagramOwnerMap = diagramDTOS.stream().collect(Collectors.groupingBy(e->e.getDiagram().getOwnerCode()));

        // 全量的私有库CI/RLT数据
        List<ESCIInfo> allPrivateCiInfos = new ArrayList<>();
        List<ESCIRltInfo> allPrivateRltInfos = new ArrayList<>();

        for (String userCode : diagramOwnerMap.keySet()) {
            List<ESDiagramDTO> diagramDtosByUserCode = diagramOwnerMap.get(userCode);
            Long domainId = diagramDtosByUserCode.get(0).getDiagram().getDomainId();
            List<ESDiagramInfoDTO> diagramInfoList = diagramDtosByUserCode.stream().map(e -> e.getDiagram()).collect(Collectors.toList());
            // 查询视图上的 CI / RLT 数据 (私有库)
            Set<String> privateCICodes = DataModelDiagramUtil.getDiagramCiList(diagramInfoList);
            Set<String> privateRltCodes = DataModelDiagramUtil.getDiagramRltList(diagramInfoList);
            privateRltCodes.addAll(mergePreProcessor.queryRltCode(diagramDtosByUserCode));
            // 查询私有库数据私有库
            List<ESCIInfo> privateCiInfos = getPrivateCI(privateCICodes, userCode);
            allPrivateCiInfos.addAll(privateCiInfos);
            List<ESCIRltInfo> privateRltInfos = getPrivateRlt(privateRltCodes, userCode);
            allPrivateRltInfos.addAll(privateRltInfos);
        }
        log.info("############需要处理的CI数量【{}】，RLT数量【{}】", allPrivateCiInfos.size(), allPrivateRltInfos.size());

        // CI历史库创建当前状态快照数据
        if (!BinaryUtils.isEmpty(allPrivateCiInfos)) {
            Map<String, Long> ciCodeVersionMap = generateCISnapshot(allPrivateCiInfos);
            List<ESDiagramNode> allDiagramNode = esDiagramNodeDao.getListByQuery(QueryBuilders.termsQuery("diagramId", snapshotIds));
            // todo 流程建模数据不在node上
            for (ESDiagramNode node : allDiagramNode) {
                Long version = ciCodeVersionMap.get(node.getCiCode());
                if (!BinaryUtils.isEmpty(version)) {
                    node.setVersion(version);
                } else {
                    // 暂时不需要处理
                }
//                    // ER图模型刷新节点内的节点
//                    if (!BinaryUtils.isEmpty(node.getNodeJson())) {
//                        DiagramNodeJson nodeJson = JSON.parseObject(node.getNodeJson(), DiagramNodeJson.class);
//                        List<DataModelEntityNodeVo> erItems = nodeJson.getItems();
//                        if (!BinaryUtils.isEmpty(erItems)) {
//                            // 遍历er图内的对象 将版本号绑定到对象内
//                            for (DataModelEntityNodeVo dataNode : erItems) {
//                                Long erVersion = ciCodeVersionMap.get(dataNode.getCiCode());
//                                if (!BinaryUtils.isEmpty(erVersion)) {
//                                    dataNode.setVersion(erVersion);
//                                }
//                            }
//                            // 更新 er Items
//                        }
//                    }
            }
            esDiagramNodeDao.saveOrUpdateBatch(allDiagramNode);
        }
        // RLT历史库创建当前状态快照数据
        if (!BinaryUtils.isEmpty(allPrivateRltInfos)) {
            Map<String, Long> rltCodeVersionMap = generateRltSnapshot(allPrivateRltInfos);
            List<ESDiagramLink> allDiagramLink = esDiagramLinkDao.getListByQuery(QueryBuilders.termsQuery("diagramId", snapshotIds));
            for (ESDiagramLink link : allDiagramLink) {
                Long version = rltCodeVersionMap.get(link.getUniqueCode());
                if (!BinaryUtils.isEmpty(version)) {
                    link.setVersion(version);
                } else {
                    // 暂时不需要处理
                }
            }
            esDiagramLinkDao.saveOrUpdateBatch(allDiagramLink);
        }
        log.info("################# 视图内的数据生成快照成功 diagramIds：【{}】", JSONObject.toJSONString(diagramIds));
        // 检查如果自动创建历史视图超过20个，则删除最早的那个视图
        deleteDiagramVersion(diagramSnapshotMap.keySet(), currentUserInfo);
    }

    /**
     * 删除自动版本超过20个的版本
     */
    private void deleteDiagramVersion(Collection<Long> diagramIds, SysUser currentUserInfo) {
        CVcDiagramVersion cVcDiagramVersion = new CVcDiagramVersion();
        cVcDiagramVersion.setDiagramIds(diagramIds.toArray(new Long[0]));
        cVcDiagramVersion.setAutoCreat(1);
        List<VcDiagramVersion> allTmpVersionList = diagramVersionDao.selectList(cVcDiagramVersion, "VERSION_TIME DESC");
        Map<Long, List<VcDiagramVersion>> tmpVersionMap = allTmpVersionList.stream().collect(Collectors.groupingBy(VcDiagramVersion::getDiagramId));
        for (Long diagramId : tmpVersionMap.keySet()) {
            //去除当前版本
            List<VcDiagramVersion> tmpVersionList = tmpVersionMap.get(diagramId);
            List<VcDiagramVersion> diagramVersionList = tmpVersionList
                    .stream()
                    .filter(version -> !version.getId().equals(version.getDiagramId()))
                    .collect(Collectors.toList());

            if (diagramVersionList.size() > 20) {
                int count = 0;
                for (int i = 0; i < diagramVersionList.size(); i++) {
                    VcDiagramVersion version = diagramVersionList.get(i);
                    Long id = version.getId();
                    if (StringUtils.isEmpty(version.getVersionName()) && !id.equals(version.getDiagramId())) {
                        if (count > 19) {
                            diagramVersionDao.deleteByIdAsync(id, currentUserInfo.getLoginCode());
                            //暂时注掉物理删除的逻辑
                            //esDiagramSvc.deleteDiagramByIds(new Long[]{versionId});
                        }
                        count++;
                    }
                }
            }
        }
    }

    /**
     *  获取快照图详情
     * @param snapshotParam 参数
     * @return
     */
    private void querySnapshotInfo(DiagramSnapshotParam snapshotParam) {
        List<ESDiagram> listByQuery = esDiagramDao.getListByQuery(QueryBuilders.termQuery("dEnergy.keyword", snapshotParam.getSnapshotId()));
        if (BinaryUtils.isEmpty(listByQuery)) {
            throw new BinaryException("未查询到视图历史版本信息");
        }
        snapshotParam.setCurrentSnapshotInfo(listByQuery.get(0));
    }

    /**
     *  拓展获取操作源视图逻辑
     * @param snapshotParam
     */
    private void querySourceAndMainDiagramInfoNew(DiagramSnapshotParam snapshotParam) {
        VcDiagramVersion vcDiagramVersion = esDiagramVersionSvc.queryDiagramVersionById(snapshotParam.getCurrentSnapshotInfo().getId());
        // 快照所绑定的主视图信息
        Long mainDiagramId = vcDiagramVersion.getDiagramId();
        if (BinaryUtils.isEmpty(mainDiagramId)) {
            throw new BinaryException("视图版本信息异常，未查询到主视图信息");
        }
        ESDiagram mainDiagramInfo = esDiagramDao.getById(mainDiagramId);
        snapshotParam.setMainDiagramInfo(mainDiagramInfo);
        snapshotParam.setOwnerCode(mainDiagramInfo.getOwnerCode());
        // currentCode字段主要用于查询CI数据，回滚操作currentCode需要取主视图拥有者 基于版本创建取当前操作用户
        String currentCode = snapshotParam.getType().equals(ROLL_BACK) ? mainDiagramInfo.getOwnerCode() : SysUtil.getCurrentUserInfo().getLoginCode();
        snapshotParam.setCurrentCode(currentCode);
        snapshotParam.setRecentSnapshotInfo(snapshotParam.getCurrentSnapshotInfo());
    }

    /**
     *  获取当前快照之后的最近的手动快照
     *      1.若本身就是手动创建，取当前数据
     *      2.若当前快照之后无手动创建的版本 取主视图信息
     * @param snapshotParam 参数
     * @return
     */
    private void querySourceAndMainDiagramInfo(DiagramSnapshotParam snapshotParam){

        VcDiagramVersion vcDiagramVersion = esDiagramVersionSvc.queryDiagramVersionById(snapshotParam.getCurrentSnapshotInfo().getId());
        // 快照所绑定的主视图信息
        Long mainDiagramId = vcDiagramVersion.getDiagramId();
        if (BinaryUtils.isEmpty(mainDiagramId)) {
            throw new BinaryException("视图版本信息异常，未查询到主视图信息");
        }
        ESDiagram mainDiagramInfo = esDiagramDao.getById(mainDiagramId);
        snapshotParam.setMainDiagramInfo(mainDiagramInfo);
        snapshotParam.setOwnerCode(mainDiagramInfo.getOwnerCode());

        if (!BinaryUtils.isEmpty(vcDiagramVersion.getAutoCreat()) && vcDiagramVersion.getAutoCreat() == 2) {
            // 当前快照为手动创建 直接返回
            log.info("########### 以当前操作快照为数据处理源端");
            snapshotParam.setRecentSnapshotInfo(snapshotParam.getCurrentSnapshotInfo());
            return;
        }

        List<VcDiagramVersion> vcDiagramVersions = esDiagramVersionSvc.queryDiagramVersionByMainId(mainDiagramId);
        if (BinaryUtils.isEmpty(vcDiagramVersions)) {
            throw new BinaryException("视图版本信息获取失败");
        }
        for (VcDiagramVersion version : vcDiagramVersions) {
            if (vcDiagramVersion.getVersionTime()<version.getVersionTime() &&
                    BinaryUtils.isEmpty(version.getAutoCreat()) && version.getAutoCreat() == 2) {
                // 返回当前操作快照后最临近的手动快照ID
                log.info("########### 当前操作快照为自动创建，获取其后最近手动创建数据为源端 versionTime:【{}】", version.getVersionTime());
                snapshotParam.setRecentSnapshotInfo(esDiagramDao.getById(version.getId()));
                return;
            }
        }
    }

    /**
     *  根据快照ID获取 CI/RLT历史数据
     * @param snapshotParam 参数
     */
    private void querySourceCiAndRltInfo(DiagramSnapshotParam snapshotParam) {
        if (BinaryUtils.isEmpty(snapshotParam.getRecentSnapshotInfo())) {
            // 空标识当前操作视图为自动创建版本 并且 之后也不存在手动创建的数据版本 所以不需要考虑数据恢复的逻辑
            log.info("当前操作视图为自动创建版本并且之后也不存在手动创建的数据版本");
            return;
        }
        ESDiagram recentSnapshotInfo = snapshotParam.getRecentSnapshotInfo();
        String ownerCode = snapshotParam.getOwnerCode();

        List<ESDiagramNode> nodeList = esDiagramNodeDao.getListByQuery(QueryBuilders.termQuery("dEnergy.keyword", recentSnapshotInfo.getDEnergy()));
        List<String> nohasVersionCIList = new ArrayList<>();
        Map<String, ESDiagramNode> ciCodeNodeMap = new HashMap<>();
        for (ESDiagramNode node : nodeList) {
            String ciCode = node.getCiCode();
            Long version = node.getVersion();
            if (!BinaryUtils.isEmpty(ciCode)) {
                if (BinaryUtils.isEmpty(version)) {
                    // 记录一下 输出日志
                    nohasVersionCIList.add(ciCode);
                } else {
                    ciCodeNodeMap.put(ciCode, node);
                }
            }
        }
        if (!BinaryUtils.isEmpty(ciCodeNodeMap)) {
            snapshotParam.setRecentNodeList(ciCodeNodeMap.values());
            BoolQueryBuilder ciHistoryQuery = QueryBuilders.boolQuery();
            for (String ciCode : ciCodeNodeMap.keySet()) {
                ESDiagramNode node = ciCodeNodeMap.get(ciCode);
                BoolQueryBuilder oneQuery = QueryBuilders.boolQuery();
                oneQuery.must(QueryBuilders.termQuery("version", node.getVersion()));
                oneQuery.must(QueryBuilders.termQuery("ciCode.keyword", ciCode));
                oneQuery.must(QueryBuilders.termQuery("ownerCode.keyword", ownerCode));
                ciHistoryQuery.should(oneQuery);
            }
            List<ESCIHistoryInfo> snapshotCIList = ciHistorySvc.getListByQuery(ciHistoryQuery);
            snapshotParam.setRecentSnapshotCIList(snapshotCIList);
        }

        List<ESDiagramLink> linkList = esDiagramLinkDao.getListByQuery(QueryBuilders.termsQuery("dEnergy.keyword", recentSnapshotInfo.getDEnergy()));
        List<String> nohasVersionRLTList = new ArrayList<>();
        Map<String, ESDiagramLink> uniqueCodeLinkMap = new HashMap<>();
        for (ESDiagramLink link : linkList) {
            String uniqueCode = link.getUniqueCode();
            Long version = link.getVersion();
            if (!BinaryUtils.isEmpty(uniqueCode)) {
                if (BinaryUtils.isEmpty(version)) {
                    nohasVersionRLTList.add(uniqueCode);
                } else {
                    uniqueCodeLinkMap.put(uniqueCode, link);
                }
            }
        }
        if (!BinaryUtils.isEmpty(uniqueCodeLinkMap)) {
            snapshotParam.setRecentLinkList(uniqueCodeLinkMap.values());
            BoolQueryBuilder rltHistoryQuery = QueryBuilders.boolQuery();
            for (String uniqueCode : uniqueCodeLinkMap.keySet()) {
                ESDiagramLink link = uniqueCodeLinkMap.get(uniqueCode);
                BoolQueryBuilder oneQuery = QueryBuilders.boolQuery();
                oneQuery.must(QueryBuilders.termQuery("version", link.getVersion()));
                oneQuery.must(QueryBuilders.termQuery("uniqueCode.keyword", uniqueCode));
                oneQuery.must(QueryBuilders.termQuery("ownerCode.keyword", ownerCode));
                rltHistoryQuery.should(oneQuery);
            }
            List<ESCIRltInfoHistory> snapshotRltList = rltHistorySvc.getListByQuery(rltHistoryQuery);
            snapshotParam.setRecentSnapshotRLTList(snapshotRltList);
        }

        // 输出异常节点信息 正常不会存在
        if (!BinaryUtils.isEmpty(nohasVersionCIList)) {
            log.info("当前源端快照数据存在异常节点 snapshotId:【{}】，exceptionNode:【{}】", recentSnapshotInfo.getId(), JSONObject.toJSONString(nohasVersionCIList));
        }
        if (!BinaryUtils.isEmpty(nohasVersionRLTList)) {
            log.info("当前源端快照数据存在异常关系 snapshotId:【{}】，exceptionLink:【{}】", recentSnapshotInfo.getId(), JSONObject.toJSONString(nohasVersionRLTList));
        }
    }

    /**
     *  历史CI/RLT数据覆盖到当前主视图所属用户的私有库
     * @param snapshotParam
     */
    private void updateLatestCiAndRlt(DiagramSnapshotParam snapshotParam) {
        log.info("#####回滚数据##### 操作视图ID：【{}】", snapshotParam.getSnapshotId());
        // CI历史数据恢复
        List<ESCIHistoryInfo> recentSnapshotCIList = snapshotParam.getRecentSnapshotCIList();
        if (!BinaryUtils.isEmpty(recentSnapshotCIList)) {
            log.info("#####回滚数据##### 操作视图内的历史CI数据总数量为：【{}】", recentSnapshotCIList.size());
            // 避免用户在版本之后删除在创建或检出覆盖的情况 根据历史库的业务主键字段匹配用户的私有库CI
            // todo：根据历史数据的【主键】字段去回滚私有库 但是存在CI数据的status!=2(未填写数据主键即生成历史版本)操作就无法回滚，兼容这种情况 再使用【ID】字段兜底，依然无法处理 status = 0 的数据创建——删除——创建的操作。。。
            List<List<String>> ciPrimaryKeyList = new ArrayList<>();
            List<Long> historyCiId = new ArrayList<>();
            for (ESCIHistoryInfo esciHistoryInfo : recentSnapshotCIList) {
                if (esciHistoryInfo.getState() == 0) {
                    historyCiId.add(esciHistoryInfo.getId());
                } else {
                    if (!BinaryUtils.isEmpty(esciHistoryInfo.getCiPrimaryKey())) {
                        List<String> onePrimaryKey = JSONArray.parseArray(esciHistoryInfo.getCiPrimaryKey(), String.class);
                        onePrimaryKey = onePrimaryKey.stream().filter(Objects::nonNull).collect(Collectors.toList());
                        if (!BinaryUtils.isEmpty(onePrimaryKey)) {
                            onePrimaryKey.add(snapshotParam.getCurrentCode());
                            ciPrimaryKeyList.add(onePrimaryKey);
                        }
                    }
                }
            }
            // 获取用户私有库对应数据
            List<ESCIInfo> privateCIInfoList = iamsESCIPrivateSvc.getCIInfoListByCIPrimaryKeys(ciPrimaryKeyList);
            log.info("#####回滚数据##### 根据历史CI数据的业务主键，在私有库获取数据数量：【{}】", privateCIInfoList.size());
            if (!BinaryUtils.isEmpty(historyCiId)) {
                CCcCi cdt = new CCcCi();
                cdt.setIds(historyCiId.toArray(new Long[0]));
                cdt.setOwnerCodeEqual(snapshotParam.getCurrentCode());
                List<ESCIInfo> historyCIInfoByIds = iamsESCIPrivateSvc.queryESCiInfoList(cdt, null, Boolean.FALSE);
                if (!BinaryUtils.isEmpty(historyCIInfoByIds)) {
                    privateCIInfoList.addAll(historyCIInfoByIds);
                }
                log.info("#####回滚数据##### 根据历史CI数据的id标识，在私有库获取数据数量：【{}】", historyCIInfoByIds.size());
            }
            Map<String, ESCIInfo> primaryKeyAndCIMap = privateCIInfoList.stream().collect(Collectors.toMap(ESCIInfo::getCiPrimaryKey, e->e, (k1, k2)->k2));
            Map<Long, ESCIInfo> idAndCIMap = privateCIInfoList.stream().collect(Collectors.toMap(ESCIInfo::getId, e->e, (k1, k2)->k2));

            // 获取当前所有私有库存在数据的分类信息
            Map<Long, ESCIClassInfo> ciClassAndIdMap = new HashMap<>();
            List<Long> classId = privateCIInfoList.stream().map(ESCIInfo::getClassId).collect(Collectors.toList());
            if (!BinaryUtils.isEmpty(classId)) {
                List<ESCIClassInfo> esciClassInfos = ciClassApiSvc.selectCiClassByIds(classId);
                ciClassAndIdMap = esciClassInfos.stream().collect(Collectors.toMap(ESCIClassInfo::getId, cla->cla, (k1,k2)->k1));
            }
            // 将历史库数据覆盖到用户私有库
            List<ESCIInfo> updateCIDate = new ArrayList<>();
            List<String> delNodeList = new ArrayList<>();
            AtomicInteger attrCIEqualNum = new AtomicInteger();
            for (ESCIHistoryInfo historyInfo : recentSnapshotCIList) {
                if (BinaryUtils.isEmpty(primaryKeyAndCIMap.get(historyInfo.getCiPrimaryKey())) &&
                        BinaryUtils.isEmpty(idAndCIMap.get(historyInfo.getId()))) {
                    // 当前历史库数据在用户私有库已经删除 图上元素重置为图片即可 记录删除元素ciCode 刷新视图节点
                    delNodeList.add(historyInfo.getCiCode());
                    continue;
                }
                ESCIInfo privateCIInfo = BinaryUtils.isEmpty(primaryKeyAndCIMap.get(historyInfo.getCiPrimaryKey())) ?
                        idAndCIMap.get(historyInfo.getId()) : primaryKeyAndCIMap.get(historyInfo.getCiPrimaryKey());

                // 检查数据attr重复
                if (checkCIAttrsEqual(privateCIInfo.getAttrs(), historyInfo.getAttrs())) {
                    attrCIEqualNum.incrementAndGet();
                    continue;
                }
                // 重置属性信息
                ESCIClassInfo classInfo = ciClassAndIdMap.get(privateCIInfo.getClassId());
                privateCIInfo.setAttrs(resetCIAttrs(privateCIInfo.getAttrs(), historyInfo.getAttrs(), classInfo));
                privateCIInfo.setLocalVersion(1L);
                privateCIInfo.setPublicVersion(historyInfo.getPublicVersion());
                privateCIInfo.setCiCode(historyInfo.getCiCode());
                if (BinaryUtils.isEmpty(primaryKeyAndCIMap.get(historyInfo.getCiPrimaryKey()))) {
                    privateCIInfo.setCiPrimaryKey(historyInfo.getCiPrimaryKey());
                }
                updateCIDate.add(privateCIInfo);
            }
            if (!BinaryUtils.isEmpty(updateCIDate)) {
                // 重置私有库CI数据
                iamsESCIPrivateSvc.saveOrUpdateBatch(updateCIDate);
                log.info("#####回滚数据##### 成功回滚CI数据数量：【{}】,CI历史库属性与私有库相同，无需处理数量：【{}】", updateCIDate.size(), attrCIEqualNum.get());
            } else {
                log.info("#####回滚数据##### 私有库CI数据未发生无覆盖操作");
            }
            snapshotParam.setDelNodeList(delNodeList);
        } else {
            log.info("#####回滚数据##### 操作视图未获取到对应的历史CI数据，无需回滚CI数据，将数据处理为普通图形");
            if (!BinaryUtils.isEmpty(snapshotParam.getRecentNodeList())) {
                List<String> delNodeList = snapshotParam.getRecentNodeList().stream().map(ESDiagramNode::getCiCode).collect(Collectors.toList());
                snapshotParam.setDelNodeList(delNodeList);
            }
        }
        log.info("#####回滚数据##### 清理node节点数据标识数量：【{}】", snapshotParam.getDelNodeList().size());

        // RLT历史数据恢复
        List<ESCIRltInfoHistory> recentSnapshotRLTList = snapshotParam.getRecentSnapshotRLTList();
        if (!BinaryUtils.isEmpty(recentSnapshotRLTList)) {
            log.info("#####回滚数据##### 操作视图内的历史RLT数据总数量为：【{}】", recentSnapshotRLTList.size());
            // todo 恢复数据先考虑正常操作的情况 根据 uniqueCode 取私有库匹配
            Set<String> historyUniqueCodeList = new HashSet<>();
            for (ESCIRltInfoHistory historyRltInfo : recentSnapshotRLTList) {
                if (BinaryUtils.isEmpty(historyRltInfo.getUniqueCode())) {
                    continue;
                }
                historyUniqueCodeList.add(historyRltInfo.getUniqueCode());
            }
            List<ESCIRltInfo> privateRltInfoList = iamsCIRltPrivateSvc.getRltByUniqueCodes(historyUniqueCodeList, snapshotParam.getCurrentCode());
            log.info("#####回滚数据##### 根据历史RLT数据的UNIQUE_CODE，在私有库获取数据数量：【{}】", privateRltInfoList.size());
            Map<String, ESCIRltInfo> uniqueCodeAndDataMap = privateRltInfoList.stream().collect(Collectors.toMap(ESCIRltInfo::getUniqueCode, e->e, (k1, k2)->k2));

            List<ESCIRltInfo> updateRltList = new ArrayList<>();
            List<String> delLinkList = new ArrayList<>();
            AtomicInteger attrRltEqual = new AtomicInteger();
            for (ESCIRltInfoHistory rltInfoHistory : recentSnapshotRLTList) {
                if (BinaryUtils.isEmpty(uniqueCodeAndDataMap.get(rltInfoHistory.getUniqueCode()))) {
                    delLinkList.add(rltInfoHistory.getUniqueCode());
                    continue;
                }
                ESCIRltInfo esciRltInfo = uniqueCodeAndDataMap.get(rltInfoHistory.getUniqueCode());
                if (CheckAttrUtil.checkAttrMapEqual(esciRltInfo.getAttrs(), rltInfoHistory.getAttrs(), Boolean.FALSE)) {
                    attrRltEqual.incrementAndGet();
                    continue;
                }
                esciRltInfo.setAttrs(resetRltAttrs(esciRltInfo.getAttrs(), rltInfoHistory.getAttrs()));
                esciRltInfo.setLocalVersion(1L);
                updateRltList.add(esciRltInfo);
            }

            if (!BinaryUtils.isEmpty(updateRltList)) {
                iamsCIRltPrivateSvc.bindBatchCiRlt(updateRltList, snapshotParam.getCurrentCode());
                log.info("#####回滚数据##### 成功回滚RLT数据数量：【{}】,RLT历史库属性与私有库相同，无需处理数量：【{}】", updateRltList.size(), attrRltEqual.get());
            } else {
                log.info("#####回滚数据##### 私有库RLT数据未发生无覆盖操作");
            }
            snapshotParam.setDelLinkList(delLinkList);
        } else {
            log.info("#####回滚数据##### 操作视图未获取到对应的历史RLT数据，无需回滚RLT数据，将数据处理为普通线段");
            if (!BinaryUtils.isEmpty(snapshotParam.getRecentLinkList())) {
                List<String> delLinkList = snapshotParam.getRecentLinkList().stream().map(ESDiagramLink::getUniqueCode).collect(Collectors.toList());
                snapshotParam.setDelLinkList(delLinkList);
            }
        }
        log.info("#####回滚数据##### 清理link节点数据标识数量：【{}】", snapshotParam.getDelLinkList().size());
    }

    /**
     *  校验CI属性重复
     * @param oldAttrs
     * @param newAttrs
     * @return
     */
    private Boolean checkCIAttrsEqual(Map<String, Object> oldAttrs, Map<String, Object> newAttrs) {
        newAttrs.put("所属用户", "");
        oldAttrs.put("所属用户", "");
        if (newAttrs.size() != oldAttrs.size()) {
            return false;
        }
        Map<String, Object> checkNewAttrs = newAttrs.entrySet().stream().filter((e) -> !BinaryUtils.isEmpty(e.getValue())).collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
        Map<String, Object> checkOldAttrs = oldAttrs.entrySet().stream().filter((e) -> !BinaryUtils.isEmpty(e.getValue())).collect(Collectors.toMap((e) -> e.getKey(), (e) -> e.getValue()));
        Iterator<Map.Entry<String, Object>> it = checkOldAttrs.entrySet().iterator();
        while (it.hasNext()) {
            String key = it.next().getKey();
            boolean isDuplicate = (checkOldAttrs.get(key) == null && checkNewAttrs.get(key) == null)
                    || (checkOldAttrs.get(key) != null && checkNewAttrs.get(key) != null &&  Objects.equals(checkOldAttrs.get(key), checkNewAttrs.get(key)));
            if (isDuplicate) {
                it.remove();
                checkNewAttrs.remove(key);
            }
        }
        return checkNewAttrs.size() > 0 || checkOldAttrs.size() > 0 ? false : true;
    }

    /**
     *  回滚或创建目标视图信息
     * @param snapshotParam
     */
    private void saveOrUpdateDiagramInfo(DiagramSnapshotParam snapshotParam) {
        ESDiagram mainDiagramInfo = snapshotParam.getMainDiagramInfo();
        ESDiagram currentSnapshotInfo = snapshotParam.getCurrentSnapshotInfo();
        if (snapshotParam.getType().equals(ROLL_BACK)) {
            esDiagramExtendSvc.rollBackDiagramById(currentSnapshotInfo, mainDiagramInfo);
            snapshotParam.setReturnDiagramId(mainDiagramInfo.getDEnergy());
        } else if (snapshotParam.getType().equals(CREATE)) {
            Map<String, String> idMap = esDiagramExtendSvc.copyDiagramBatch(new HashMap<String, Long>() {{
                put(currentSnapshotInfo.getDEnergy(), mainDiagramInfo.getDirId());
            }}, Collections.singletonList(currentSnapshotInfo), DiagramCopyEnum.SNAPSHOT);
            snapshotParam.setReturnDiagramId(idMap.get(currentSnapshotInfo.getDEnergy()));
        } else {
            log.error("操作参数异常" + snapshotParam.getType());
        }
    }

    /**
     *  刷新目标视图内可能存在的异常节点信息
     * @param snapshotParam
     */
    private void refreshDiagramNodeAndLink(DiagramSnapshotParam snapshotParam) {
        if (BinaryUtils.isEmpty(snapshotParam.getReturnDiagramId())) {
            throw new ServerException("视图回滚异常");
        }
        if (!BinaryUtils.isEmpty(snapshotParam.getDelNodeList())) {
            BoolQueryBuilder boolNode = QueryBuilders.boolQuery();
            boolNode.must(QueryBuilders.termQuery("dEnergy.keyword", snapshotParam.getReturnDiagramId()));
            boolNode.must(QueryBuilders.termsQuery("ciCode.keyword", snapshotParam.getDelNodeList()));
            List<ESDiagramNode> refreshDelNodeList = esDiagramNodeDao.getListByQuery(boolNode);
            // 删除不存在的数据节点
            refreshNode(refreshDelNodeList);
        }

        if (!BinaryUtils.isEmpty(snapshotParam.getDelLinkList())) {
            BoolQueryBuilder boolLink = QueryBuilders.boolQuery();
            boolLink.must(QueryBuilders.termQuery("dEnergy.keyword", snapshotParam.getReturnDiagramId()));
            boolLink.must(QueryBuilders.termsQuery("uniqueCode.keyword", snapshotParam.getDelLinkList()));
            List<ESDiagramLink> refreshDelLinkList = esDiagramLinkDao.getListByQuery(boolLink);
            // 删除不存在的数据节点
            refreshLink(refreshDelLinkList);
        }
    }

    /**
     *  刷新 CI节点
     * @param refreshDelNodeList
     */
    private void refreshNode(List<ESDiagramNode> refreshDelNodeList) {
        for (ESDiagramNode delNode : refreshDelNodeList) {
            delNode.setCiCode("");
            // node_json内的数据信息制空
            JSONObject nodeJson = JSONObject.parseObject(delNode.getNodeJson());
            if (!BinaryUtils.isEmpty(nodeJson.get("ciCode"))) {
                nodeJson.remove("ciCode");
            }
            if (!BinaryUtils.isEmpty(nodeJson.get("ciId"))) {
                nodeJson.remove("ciId");
            }
            delNode.setNodeJson(JSON.toJSONString(nodeJson));
        }
        esDiagramNodeDao.saveOrUpdateBatch(refreshDelNodeList);
    }

    /**
     *  刷新 RLT节点
     * @param refreshDelLinkList
     */
    private void refreshLink(List<ESDiagramLink> refreshDelLinkList) {
        for (ESDiagramLink delLink : refreshDelLinkList) {
            delLink.setUniqueCode("");
            // link_json内的数据信息制空
            JSONObject linkJson = JSONObject.parseObject(delLink.getLinkJson());
            if (!BinaryUtils.isEmpty(linkJson.get("rltCode"))) {
                linkJson.remove("rltCode");
            }
            if (!BinaryUtils.isEmpty(linkJson.get("rltId"))) {
                linkJson.remove("rltId");
            }
            delLink.setLinkJson(JSON.toJSONString(linkJson));
        }
        esDiagramLinkDao.saveOrUpdateBatch(refreshDelLinkList);
    }

    /**
     *  重置私有库CI属性字段
     * @param privateAttrs
     * @param privateHistoryAttrs
     * @param classInfo 分类信息
     * @return
     */
    private Map<String, Object> resetCIAttrs(Map<String, Object> privateAttrs, Map<String, Object> privateHistoryAttrs, ESCIClassInfo classInfo) {
        List<CcCiAttrDef> attrDefs = classInfo.getCcAttrDefs();
        Map<String, CcCiAttrDef> attrDefMap = attrDefs.stream().collect(Collectors.toMap(CcCiAttrDef::getProStdName, attrD->attrD, (K1, K2)->K1));
        Map<String, Object> resetAttrs = new HashMap<>();
        for (String key : privateAttrs.keySet()) {
            CcCiAttrDef attrDef = attrDefMap.get(key.toUpperCase());
            if (BinaryUtils.isEmpty(attrDef)) {
                continue;
            }
            if (!BinaryUtils.isEmpty(privateHistoryAttrs.get(key))) {
                Integer success = CheckAttrUtil.validateAttrValType(attrDef, privateHistoryAttrs.get(key).toString());
                if (1 == success) {
                    resetAttrs.put(key, privateHistoryAttrs.get(key));
                } else {
                    log.info("#####回滚数据##### 分类code：【{}】，历史库覆盖私有库数据字段：【{}】异常，原因：【{}】", classInfo.getClassCode(), key, success);
                }
            } else {
                // 清空当前字段内容
                log.info("#####回滚数据##### 分类code：【{}】，历史库数据不存在当前分类字段：【{}】", classInfo.getClassCode(), key);
                resetAttrs.put(key, null);
            }
        }
        return resetAttrs;
    }

    /**
     *  重置私有库RLT属性字段
     * @param privateAttrs
     * @param privateHistoryAttrs
     * @return
     */
    private Map<String, String> resetRltAttrs(Map<String, String> privateAttrs, Map<String, String> privateHistoryAttrs) {
        Map<String, String> resetAttrs = new HashMap<>();
        for (String key : privateAttrs.keySet()) {
            if (!BinaryUtils.isEmpty(privateHistoryAttrs.get(key))) {
                resetAttrs.put(key, privateHistoryAttrs.get(key));
            } else {
                resetAttrs.put(key, null);
            }
        }
        return resetAttrs;
    }

    /**
     *  在【uino_cmdb_ci_history_private】中生成数据 并返回对应的版本信息
     *  如果当前数据与历史库内最大版本数据内容相同 不需要生成快照 直接返回历史库最大版本数据
     * @param ciInfos CI数据
     * @return CI_CODE 与 历史库中对应CI数据的版本
     */
    public Map<String, Long> generateCISnapshot(List<ESCIInfo> ciInfos) {
        Map<String, Long> ciCodeVersionMap = new HashMap<>();
        // 根据 ciId 查询历史库当前CI的最大历史版本数据 对比历史版本与当前数据属性 若有修改生成版本 无修改直接取最大版本号
        List<Long> ciIds = ciInfos.stream().map(ESCIInfo::getId).collect(Collectors.toList());
        Map<Long, ESCIHistoryInfo> idDataMap = ciHistorySvc.queryMaxVersionDataByCiId(ciIds);
        List<ESCIInfo> needCreatHistoryList = new ArrayList<>();
        Map<Long, Long> needCreatIdVersionMap = new HashMap<>();
        for (ESCIInfo esciInfo : ciInfos) {
            Long ciId = esciInfo.getId();
            ESCIHistoryInfo esciHistoryInfo = idDataMap.get(ciId);
            if (BinaryUtils.isEmpty(esciHistoryInfo)) {
                needCreatHistoryList.add(esciInfo);
                needCreatIdVersionMap.put(ciId, 1L);
                ciCodeVersionMap.put(esciInfo.getCiCode(), 1L);
            } else {
                // 对比属性是否存在变化
                Map<String, String> historyAttrs = toStdMap(EamUtil.coverToAttrs(esciHistoryInfo.getAttrs()));
                Map<String, String> curAttrs = toStdMap(EamUtil.coverToAttrs(esciInfo.getAttrs()));
                if (CheckAttrUtil.checkAttrMapEqual(historyAttrs, curAttrs, false)) {
                    // 与历史库最大版本内容相同 无需生成历史数据
                    ciCodeVersionMap.put(esciInfo.getCiCode(), esciHistoryInfo.getVersion());
                } else {
                    needCreatHistoryList.add(esciInfo);
                    needCreatIdVersionMap.put(ciId, esciHistoryInfo.getVersion() + 1);
                    ciCodeVersionMap.put(esciInfo.getCiCode(), esciHistoryInfo.getVersion() + 1);
                }
            }
        }
        // 创建历史版本 异步处理
        if (!BinaryUtils.isEmpty(needCreatHistoryList)) {
            ciHistorySvc.saveOrUpdateHistoryInfosBatchById(needCreatHistoryList, needCreatIdVersionMap, ESCIHistoryInfo.ActionType.SAVE_OR_UPDATE);
        }
        log.info("#############CI数据快照ciCodeVersionMap【{}】", JSONObject.toJSONString(ciCodeVersionMap));
        return ciCodeVersionMap;
    }

    /**
     *  在【uino_cmdb_ci_rlt_history_private】中生成数据 并返回对应的版本信息
     *  如果当前数据与历史库内最大版本数据内容相同 不需要生成快照 直接返回历史库最大版本数据
     * @param rltInfos RLT数据
     * @return RLT_CODE 与 历史库中对应RLT数据的版本
     */
    public Map<String, Long> generateRltSnapshot(List<ESCIRltInfo> rltInfos) {
        Map<String, Long> rltCodeVersionMap = new HashMap<>();

        // 这里需要考虑一下当CI主键冲突时，会连带把连接CI的关系重置并刷新节点的数据 这里先取关系线源端目标端和分类的ID去搞
        List<String> uniqueIds = new ArrayList<>();
        for (ESCIRltInfo esciRltInfo : rltInfos) {
            uniqueIds.add(esciRltInfo.getSourceCiId() + "_" + esciRltInfo.getClassId() + "_" + esciRltInfo.getTargetCiId());
        }
        Map<String, ESCIRltInfoHistory> uniqueIdDataMap = rltHistorySvc.queryMaxVersionDataByUniqueId(uniqueIds);
        List<ESCIRltInfo> needCreatHistoryList = new ArrayList<>();
        Map<String, Long> needCreatUniqueIdVersionMap = new HashMap<>();
        for (ESCIRltInfo esciRltInfo : rltInfos) {
            String uniqueId = esciRltInfo.getSourceCiId() + "_" + esciRltInfo.getClassId() + "_" + esciRltInfo.getTargetCiId();
            ESCIRltInfoHistory esciRltInfoHistory = uniqueIdDataMap.get(uniqueId);
            if (BinaryUtils.isEmpty(esciRltInfoHistory)) {
                needCreatHistoryList.add(esciRltInfo);
                needCreatUniqueIdVersionMap.put(uniqueId, 1L);
                rltCodeVersionMap.put(esciRltInfo.getUniqueCode(), 1L);
            } else {
                // 对比属性是否存在变化
                Map<String, String> historyAttrs = toStdMap(esciRltInfoHistory.getAttrs());
                Map<String, String> curAttrs = toStdMap(esciRltInfo.getAttrs());
                if (CheckAttrUtil.checkAttrMapEqual(historyAttrs, curAttrs, false)) {
                    // 与历史库最大版本内容相同 无需生成历史数据
                    rltCodeVersionMap.put(esciRltInfo.getUniqueCode(), esciRltInfoHistory.getVersion());
                } else {
                    needCreatHistoryList.add(esciRltInfo);
                    Long versionNum = esciRltInfoHistory.getVersion() + 1;
                    needCreatUniqueIdVersionMap.put(uniqueId, versionNum);
                    rltCodeVersionMap.put(esciRltInfo.getUniqueCode(), versionNum);
                }
            }
        }
        // 创建历史版本 异步处理
        if (!BinaryUtils.isEmpty(needCreatHistoryList)) {
            rltHistorySvc.saveCIRltHistoryByCIRlts(needCreatHistoryList, needCreatUniqueIdVersionMap);
        }
        log.info("###########rltCodeVersionMap【{}】", JSONObject.toJSONString(rltCodeVersionMap));
        return rltCodeVersionMap;
    }

    /**
     *  生成视图快照
     *  数据逻辑与画布历史版本保持一致
     * @param diagramIds 视图ID
     * @return 视图ID 与 快照视图ID
     */
    public Map<Long, Long> generateDiagramSnapshot(Collection<String> diagramIds) {
        Map<Long, Long> paramMap = new HashMap<>();
        List<ESDiagram> esDiagrams = esDiagramSvc.queryDBDiagramInfoByIds(diagramIds.toArray(new String[0]));

        // 预设快照id
        if (BinaryUtils.isEmpty(esDiagrams)) {
            return paramMap;
        }
        for (ESDiagram esDiagram : esDiagrams) {
            // 过滤资产仓库视图
            if (esDiagram.getIsOpen() == 1) continue;
            Long uuid = ESUtil.getUUID();
            paramMap.put(esDiagram.getId(), uuid);
        }
        return paramMap;
    }

    /**
     *  根据CI_CODE查询私有库CI信息
     * @param ciCodeList ciCode集合
     * @return ci数据
     */
    private List<ESCIInfo> getPrivateCI(Collection<String> ciCodeList, String userCode) {
        if (BinaryUtils.isEmpty(ciCodeList) || BinaryUtils.isEmpty(userCode)) {
            return new ArrayList<>();
        }
        CCcCi cdt = new CCcCi();
        cdt.setCiCodes(ciCodeList.toArray(new String[0]));
        cdt.setOwnerCodeEqual(userCode);
        List<ESCIInfo> ccCiInfos = iamsESCIPrivateSvc.queryESCiInfoList(cdt, null, Boolean.FALSE);
        return ccCiInfos;
    }

    /**
     *  根据RLT_CODE查询私有库RLT信息
     * @param rltCodeList rltCode集合
     * @return rlt数据
     */
    private List<ESCIRltInfo> getPrivateRlt(Set<String> rltCodeList, String userCode) {
        if (BinaryUtils.isEmpty(rltCodeList) || BinaryUtils.isEmpty(userCode)) {
            return new ArrayList<>();
        }
        ESRltSearchBean esRltSearchBean = new ESRltSearchBean();
        esRltSearchBean.setOwnerCode(userCode);
        esRltSearchBean.setRltUniqueCodes(rltCodeList);
        esRltSearchBean.setPageSize(rltCodeList.size());
        esRltSearchBean.setPageNum(1);
        Page<ESCIRltInfo> esciRltInfoPage = iamsCIRltPrivateSvc.searchRlt(esRltSearchBean);
        return esciRltInfoPage.getData();
    }

    /**
     *  获取用户具体信息
     * @param userCode
     */
    private SysUser queryCurUserInfoByCode(String userCode) {
        CSysUser userCdt = new CSysUser();
        userCdt.setLoginCodeEqual(userCode);
        List<SysUser> userInfoByCdt = iUserApiSvc.getSysUserByCdt(userCdt);
        if (BinaryUtils.isEmpty(userInfoByCdt)) {
            throw new ServerException("用户详情获取异常");
        }
        return userInfoByCdt.get(0);
    }

    /**
     *  更新主视图以及主视图历史版本表中关联数据的修改时间字段
     * @param diagramIds
     */
    private void updateModifyTime(Collection<Long> diagramIds, SysUser currentUserInfo) {
        Long numberDateTime = BinaryUtils.getNumberDateTime();
        // 主视图
        List<ESDiagram> updateList = esDiagramDao.getListByQuery(QueryBuilders.termsQuery("id", diagramIds));
        if (!BinaryUtils.isEmpty(updateList)) {
            updateList.forEach(diagram->{
                diagram.setModifyTime(numberDateTime);
            });
            esDiagramDao.saveOrUpdateBatch(updateList);
        }
        // 主视图关联的历史版本数据
        CVcDiagramVersion vcDiagramVersion = new CVcDiagramVersion();
        vcDiagramVersion.setIds(diagramIds.toArray(new Long[0]));
        List<VcDiagramVersion> vcDiagramVersions = diagramVersionDao.selectList(vcDiagramVersion, null);
        if (!BinaryUtils.isEmpty(vcDiagramVersions)) {
            vcDiagramVersions.forEach(version->{
                version.setModifyTime(numberDateTime);
                diagramVersionDao.updateByIdAsync(version, version.getId(), currentUserInfo.getLoginCode());
            });
        }
    }

}